package ao.ai.ml.algo.supervised.regression.linear.parametric;

import ao.ai.ml.algo.supervised.model.example.Example;
import ao.ai.ml.algo.supervised.model.hypothesis.ext.RegressionHypothesis;
import ao.ai.ml.algo.supervised.model.hypothesis.impl.LinearHypothesis;
import ao.ai.ml.algo.supervised.regression.model.RegressionLearner;
import ao.ai.ml.model.feature_set.ext.num.NumericalFeatureList;
import ao.ai.ml.model.feature_set.ext.num.SingleNumericalFeature;
import org.apache.commons.math.linear.Array2DRowRealMatrix;
import org.apache.commons.math.linear.LUDecompositionImpl;
import org.apache.commons.math.linear.RealMatrix;
import org.apache.commons.math.linear.SingularMatrixException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
 * User: aostrovsky
 * Date: 2-Feb-2010
 * Time: 8:53:34 AM
 */
public class NormalEquationSolver
        implements RegressionLearner
{
    //--------------------------------------------------------------------
    private static final Logger LOG =
            LoggerFactory.getLogger(
            		NormalEquationSolver.class);


    //-------------------------------------------------------------------------
    @Override
    public RegressionHypothesis learn(
            List<? extends Example<? extends NumericalFeatureList,
                                   ? extends SingleNumericalFeature>>
                    data)
    {
        Example<? extends NumericalFeatureList,
                ? extends SingleNumericalFeature>
                    arbitraryExample = data.get(0);

        int featureCount = arbitraryExample.input().size();
		RealMatrix features = new Array2DRowRealMatrix(
				data.size(), featureCount + 1);

		RealMatrix targets  = new Array2DRowRealMatrix(
								data.size(), 1);

		for (int i = 0; i < data.size(); i++)
        {
            Example<? extends NumericalFeatureList,
                    ? extends SingleNumericalFeature> e = data.get(i);

            features.setEntry(i, 0, 1.0);
			for (int j = 1; j <= featureCount; j++)
            {
				features.setEntry(i, j, e.input().doubleValue(j - 1));
			}

			targets .setEntry(i, 0, e.output().doubleValue());
		}

        try
        {
            RealMatrix featureTranspose = features.transpose();
            RealMatrix parameters       =
                    new LUDecompositionImpl(
                            featureTranspose.multiply(features)
                    ).getSolver().getInverse()
                        .multiply(featureTranspose)
                        .multiply(targets);

            return new LinearHypothesis(
                     parameters.getColumn(0), arbitraryExample);
        }
        catch (SingularMatrixException ignored)
        {
            return null;
        }
    }

    
	//--------------------------------------------------------------------
	@Override public String toString() {
		return "Normal Equation Solver";
	}
}
